#include "cellclustercube.h"
#include "application.h"
#include "servicegraphics.h"
#include "graphicsmodel.h"
#include "graphicsmesh.h"
#include "planetcache.h"

namespace infinity {
	namespace render {

CellClusterCube::CellClusterCube(dw::uint32 size, Vector3i origin)
:	CellCluster(size, origin)
{
}

void CellClusterCube::initializeMesh()
{
	dw::int32 totalCube = m_size * m_size * m_size;
	dw::int32 maxCubeCount = (totalCube + 1) >> 1;
	dw::int32 maxQuadCount = 6 * maxCubeCount;
	dw::int32 maxVerticesCount = 4 * maxQuadCount;
	dw::int32 maxIndicesCount = 2 * 3 * maxQuadCount;

	m_vertices = new dw::float32[maxVerticesCount * (3 * 3 + 2)];
	m_indices = new dw::uint16[maxIndicesCount];

	curindex = 0;
	curvertex = 0;

	Planet::Cell cell;
	Planet::Cell neighbour;

	Vector3i cacheOrigin = m_origin - Vector3i(1, 1, 1); // prefetch neighbors too
	Vector3i cacheDimensions = Vector3i(m_size + 2, m_size + 2, m_size + 2); // prefetch neighbors too

	WorldGridManager& worldManager(WorldGridManager::getInstance());
	Planet::Cache cache(cacheDimensions);

	cache.prefetch(worldManager.getPlanet(), cacheOrigin, cacheDimensions);

	static const Vector3i Neighbours[WorldGridManager::N_MAX_NEIGHBOR] = {
		Vector3i(  1,  0,  0 ),
		Vector3i( -1,  0,  0 ),
		Vector3i(  0,  1,  0 ),
		Vector3i(  0, -1,  0 ),
		Vector3i(  0,  0,  1 ),
		Vector3i(  0,  0, -1 )
	};

	Vector3i cellPos;

	for (dw::uint32 indexZ = 0; indexZ < m_size; ++indexZ)
	{
		cellPos.z = indexZ + m_origin.z;
		for (dw::uint32 indexY = 0; indexY < m_size; ++indexY)
		{
			cellPos.y = indexY + m_origin.y;
			for (dw::uint32 indexX = 0; indexX < m_size; ++indexX)
			{
				cellPos.x = indexX + m_origin.x;

				cache.getCell(cellPos, cell);

				const Material& material = worldManager.getPlanet()->getMaterialLibrary().getMaterialFromId(cell.getMaterialId());

				if (!material.isVoid())
				{
					for (dw::int32 n = 0; n < WorldGridManager::N_MAX_NEIGHBOR; ++n)
					{
						cache.getCell(cellPos + Neighbours[(WorldGridManager::Neighbor)n], neighbour);

						const Material& neighbourMat = worldManager.getPlanet()->getMaterialLibrary().getMaterialFromId(neighbour.getMaterialId());
						
						if (neighbourMat.isVoid() || neighbourMat.isAlphaBlend())
						{
							addFace(float3(dw::float32(indexX), dw::float32(indexY), dw::float32(indexZ)),
								cell.getMaterialId(), (WorldGridManager::Neighbor)n);
						}
					}
				}
			}
		}
	}

	if (curindex > 0)
	{
		ServiceGraphics& svcGraphics = Application::getInstance().getService< ServiceGraphics >();

		m_model = svcGraphics.createModel(ServiceGraphics::GROUP_TERRAIN);
		
		dw::maths::mat4& transform = m_model->getTransform();

		transform.pos = dw::maths::vec4(dw::float32(m_origin.x), dw::float32(m_origin.y), dw::float32(m_origin.z), 1.0f);
		
		dw::graphics::IndexBuffer& indexBuffer = m_model->getMesh()->getIndexBuffer();

		indexBuffer.bind();
		indexBuffer.resize(curindex);
		indexBuffer.write(m_indices);
		indexBuffer.unbind();

		dw::graphics::VertexBuffer& vertexBuffer = m_model->getMesh()->getVertexBuffer();

		vertexBuffer.bind();
		vertexBuffer.resize(curvertex/11);
		vertexBuffer.write(m_vertices);
		vertexBuffer.unbind();
	}

	delete m_vertices;
	delete m_indices;
}

void CellClusterCube::addFace(float3 position, dw::byte matID, WorldGridManager::Neighbor n)
{
	static const dw::uint32 verticesCount = 8;

	_vertex vertices[verticesCount] = {
		_vertex(0.0f, 0.0f, 0.0f),
		_vertex(0.0f, 0.0f, 1.0f),
		_vertex(1.0f, 0.0f, 1.0f),
		_vertex(1.0f, 0.0f, 0.0f),
		_vertex(0.0f, 1.0f, 0.0f),
		_vertex(0.0f, 1.0f, 1.0f),
		_vertex(1.0f, 1.0f, 1.0f),
		_vertex(1.0f, 1.0f, 0.0f)
	};

	_normal normal(0.f,0.f,0.f);
	_indices indices(0,0,0,0);


	const WorldGridManager& worldManager = WorldGridManager::getInstance();
	const MaterialLibrary& materialLib = worldManager.getPlanet()->getMaterialLibrary();
	const Material& material = materialLib.getMaterialFromId(matID);
	const dw::graphics::Colorf& materialColor = material.getColor();

	float3 color(materialColor.r, materialColor.g, materialColor.b);
	dw::maths::Vector2f offset(-1.f,-1.f);

	switch (n)
	{
	case WorldGridManager::N_TOP:
		{
			normal = _normal(0.f, 1.f, 0.f);
			indices = _indices(5,4,7,6);
			offset = material.getOffsetTop();
			break;
		}
	case WorldGridManager::N_BOTTOM:
		{
			normal = _normal(0.f, -1.f, 0.f);
			indices = _indices(0,1,2,3);
			offset = material.getOffsetBottom();
			break;
		}
	case WorldGridManager::N_BACK:
		{
			normal = _normal(0.f, 0.f, -1.f);
			indices = _indices(4,0,3,7);
			offset = material.getOffsetSide();
			break;
		}
	case WorldGridManager::N_FRONT:
		{
			normal = _normal(0.f, 0.f, 1.f);
			indices = _indices(6,2,1,5);
			offset = material.getOffsetSide();
			break;
		}

	case WorldGridManager::N_LEFT:
		{
			normal = _normal(1.f, 0.f, 0.f);
			indices = _indices(7,3,2,6);
			offset = material.getOffsetSide();
			break;	
		}
	case WorldGridManager::N_RIGHT:
		{
			normal = _normal(-1.f, 0.f, 0.f);
			indices = _indices(5,1,0,4);
			offset = material.getOffsetSide();
			break;
		}
	default:
		return;
	}

	addQuad(position, &indices, &vertices[0], normal, color, offset);
}

void CellClusterCube::addQuad(float3 position, _indices* index,_vertex *vertex, _normal& normal,float3 &color, dw::maths::Vector2f &offset)
{
	dw::uint32 curVertexTmp = curvertex;

	dw::maths::Vector2f texcoord = offset;
	float sizecell = 1/16.0f;

	for (unsigned int i = 0; i < 4; ++i)
	{
		memcpy(&m_vertices[curvertex],&vertex[index->get(i)],sizeof(_vertex)); curvertex += 3;
		memcpy(&m_vertices[curvertex],&normal,sizeof(_normal)); curvertex += 3;
		memcpy(&m_vertices[curvertex],&color,sizeof(_normal)); curvertex += 3;

		if(i==1)
		{
			texcoord[0] = offset.x ;
			texcoord[1] = offset.y+ sizecell;
		}
		else if(i==2)
		{
			texcoord[0] = offset.x+ sizecell;
			texcoord[1] = offset.y+ sizecell;
		}
		else if(i==3)
		{
			texcoord[0] = offset.x+ sizecell;
			texcoord[1] = offset.y;
		}

		memcpy(&m_vertices[curvertex],&texcoord, sizeof(dw::maths::Vector2f)); curvertex += 2;

	}

	for(dw::ulong i = curVertexTmp; i< curvertex; i += (3 * 3 + 2))
	{
		m_vertices[i] += position.x;
		m_vertices[i + 1] += position.y;
		m_vertices[i + 2] += position.z;
	}

	m_indices[curindex++] =  0 + curVertexTmp/(3*3 +2);
	m_indices[curindex++] =  2 + curVertexTmp/(3*3 +2);
	m_indices[curindex++] =  1 + curVertexTmp/(3*3 +2);
	m_indices[curindex++] =  0 + curVertexTmp/(3*3 +2);
	m_indices[curindex++] =  3 + curVertexTmp/(3*3 +2);
	m_indices[curindex++] =  2 + curVertexTmp/(3*3 +2);
}

	}//namespace render
} // namespace infinity
